<!doctype html>
<html>
<head>
  <title>WebRTC：简单RTCDataChannel示例</title>
  <!--view-source:https://mdn-samples.mozilla.org/s/webrtc-simple-datachannel/-->
  <meta charset="utf-8">
  <!--view-source:https://mdn-samples.mozilla.org/s/webrtc-simple-datachannel/main.css-->
  <!--<link rel="stylesheet" href="main.css" type="text/css" media="all">-->
  <style>
    body {
      font-family: "Lucida Grande", "Arial", sans-serif;
      font-size: 16px;
    }

    .messagebox {
      border: 1px solid black;
      padding: 5px;
      width: 450px;
    }

    #message {
      width: 60%;
    }

    .buttonright {
      float: right;
    }

    .buttonleft {
      float: left;
    }

    .controlbox {
      padding: 5px;
      width: 450px;
      height: 28px;
    }
  </style>
  <!--适配器可以不要-->
  <!--view-source:https://mdn-samples.mozilla.org/s/adapter.js-->
  <!--<script src="../adapter.js"></script>-->
  <!--<script src="https://mdn-samples.mozilla.org/s/adapter.js"></script>-->
  <!--view-source:https://mdn-samples.mozilla.org/s/webrtc-simple-datachannel/main.js-->
  <!--<script src="main.js"></script>-->
  <script>
    // (function () {

    // Define "global" variables

    let connectButton = null;
    let disconnectButton = null;
    let sendButton = null;
    let messageInputBox = null;
    let receiveBox = null;

    let localConnection = null;   // RTCPeerConnection for our "local" connection
    let remoteConnection = null;  // RTCPeerConnection for the "remote"

    let sendChannel = null;       // RTCDataChannel for the local (sender)
    let receiveChannel = null;    // RTCDataChannel for the remote (receiver)

    // Functions

    // Set things up, connect event listeners, etc.

    // 页面载入初始化工作
    function startup() {
      connectButton = document.getElementById('connectButton');
      disconnectButton = document.getElementById('disconnectButton');
      sendButton = document.getElementById('sendButton');
      messageInputBox = document.getElementById('message');
      receiveBox = document.getElementById('receivebox');

      // Set event listeners for user interface widgets
      //设置用户界面组件事件监听
      connectButton.addEventListener('click', connectPeers, false);
      disconnectButton.addEventListener('click', disconnectPeers, false);
      sendButton.addEventListener('click', sendMessage, false);
    }

    // Connect the two peers. Normally you look for and connect to a remote
    // machine here, but we're just connecting two local objects, so we can
    // bypass that step.
    /*
        连接两个对等点。通常您在这里查找并连接到远程计算机，但我们只是连接两个本地对象，因此可以绕过这一步。
     */

    function connectPeers() {
      // Create the local connection and its event listeners
      // 创建本地连接及其事件侦听器

      localConnection = new RTCPeerConnection(null);

      // Create the data channel and establish its event listeners
      // 创建数据通道并建立其事件侦听器
      sendChannel = localConnection.createDataChannel("sendChannel");
      sendChannel.onopen = handleSendChannelStatusChange;
      sendChannel.onclose = handleSendChannelStatusChange;

      // Create the remote connection and its event listeners
      // 创建远程连接及其事件侦听器

      remoteConnection = new RTCPeerConnection(null);
      // ondatachannel似乎需要adapter适配?
      remoteConnection.ondatachannel = receiveChannelCallback;

      // Set up the ICE candidates for the two peers
      //为两个对等点设置ICE候选人

      localConnection.onicecandidate = e => !e.candidate
        || remoteConnection.addIceCandidate(e.candidate)
          .catch(handleAddCandidateError);

      remoteConnection.onicecandidate = e => !e.candidate
        || localConnection.addIceCandidate(e.candidate)
          .catch(handleAddCandidateError);

      // Now create an offer to connect; this starts the process
      //现在创建一个要连接的提议；这将启动该过程

      localConnection.createOffer()
        .then(offer => localConnection.setLocalDescription(offer))
        .then(() => remoteConnection.setRemoteDescription(localConnection.localDescription))
        .then(() => remoteConnection.createAnswer())
        .then(answer => remoteConnection.setLocalDescription(answer))
        .then(() => localConnection.setRemoteDescription(remoteConnection.localDescription))
        .catch(handleCreateDescriptionError);
    }

    // Handle errors attempting to create a description;
    // this can happen both when creating an offer and when
    // creating an answer. In this simple example, we handle
    // both the same way.
    /*
    处理试图创建描述的错误异常；
    创建报价和创建答复时都可能发生这种情况。
    在这个简单的例子中，我们以同样的方式处理这两个问题。
     */

    function handleCreateDescriptionError(error) {
      console.log("Unable to create an offer: " + error.toString());
    }

    // Handle successful addition of the ICE candidate
    // on the "local" end of the connection.
    // "本地"端 ICE candidate 连接成功后附加处理事件


    function handleLocalAddCandidateSuccess() {
      connectButton.disabled = true;
    }

    // Handle successful addition of the ICE candidate
    // on the "remote" end of the connection.
    // "远程"端 ICE candidate 连接成功后附加处理事件

    function handleRemoteAddCandidateSuccess() {
      disconnectButton.disabled = false;
    }

    // Handle an error that occurs during addition of ICE candidate.
    // 处理添加 ICE candidate 期间发生的错误。

    function handleAddCandidateError() {
      console.log("Oh noes! addICECandidate failed!");
    }

    // Handles clicks on the "Send" button by transmitting
    // a message to the remote peer.
    // 通过发送来处理对“发送”按钮的单击
    // 向远程对等方发送的消息。

    function sendMessage() {
      let message = messageInputBox.value;
      sendChannel.send(message);

      // Clear the input box and re-focus it, so that we're
      // ready for the next message.
      // 清除输入框并重新聚焦，以便
      // 为下一条消息做好准备。

      messageInputBox.value = "";
      messageInputBox.focus();
    }

    // Handle status changes on the local end of the data
    // channel; this is the end doing the sending of data
    // in this example.
    // 处理数据通道本地端的状态变化；
    // 在本例中，这是执行数据发送的结束。

    function handleSendChannelStatusChange(event) {
      console.log('handleSendChannelStatusChange', event);
      if (sendChannel) {
        let state = sendChannel.readyState;

        if (state === "open") {
          messageInputBox.disabled = false;
          messageInputBox.focus();
          sendButton.disabled = false;
          handleLocalAddCandidateSuccess();
          disconnectButton.disabled = false;
          handleRemoteAddCandidateSuccess();
          connectButton.disabled = true;
        } else {
          messageInputBox.disabled = true;
          sendButton.disabled = true;
          connectButton.disabled = false;
          disconnectButton.disabled = true;
        }
      }
    }

    // Called when the connection opens and the data
    // channel is ready to be connected to the remote.
    /*
    当连接打开并且数据
通道已准备好连接到远程。
     */

    function receiveChannelCallback(event) {
      console.log('receiveChannelCallback', event);
      receiveChannel = event.channel;
      receiveChannel.onmessage = handleReceiveMessage;
      receiveChannel.onopen = handleReceiveChannelStatusChange;
      receiveChannel.onclose = handleReceiveChannelStatusChange;
    }

    // Handle onmessage events for the receiving channel.
    // These are the data messages sent by the sending channel.
    /*
    处理接收通道的onmessage事件。
这些是发送通道发送的数据消息。
     */

    function handleReceiveMessage(event) {
      let el = document.createElement("p");
      let txtNode = document.createTextNode(event.data);

      el.appendChild(txtNode);
      receiveBox.appendChild(el);
    }

    // Handle status changes on the receiver's channel.
    // 处理接收器频道上的状态更改。

    function handleReceiveChannelStatusChange(event) {
      console.log('handleReceiveChannelStatusChange', event);
      if (receiveChannel) {
        console.log("Receive channel's status has changed to " +
          receiveChannel.readyState);
      }

      // Here you would do stuff that needs to be done
      // when the channel's status changes.
      /*
      在这里你可以做需要做的事情
      当频道的状态改变时。
       */
    }

    // Close the connection, including data channels if they're open.
    // Also update the UI to reflect the disconnected status.
    //关闭连接，包括打开的数据通道。
    //同时更新UI以反映断开连接的状态。

    function disconnectPeers() {

      // Close the RTCDataChannels if they're open.
      //如果RTCDataChannels打开，请关闭它们。

      sendChannel.close();
      receiveChannel.close();

      // Close the RTCPeerConnections
      // Close the RTCPeerConnections

      localConnection.close();
      remoteConnection.close();

      sendChannel = null;
      receiveChannel = null;
      localConnection = null;
      remoteConnection = null;

      // Update user interface elements
      //更新用户界面元素

      connectButton.disabled = false;
      disconnectButton.disabled = true;
      sendButton.disabled = true;

      messageInputBox.value = "";
      messageInputBox.disabled = true;
    }

    // Set up an event listener which will run the startup
    // function once the page is done loading.
    //设置将运行启动的事件侦听器
    //一旦页面加载完毕，函数即生效。

    window.addEventListener('load', startup, false);
    // })();
  </script>
</head>
<body>
<h1> MDN-WebRTC：简单RTCDataChannel示例 </h1>

<p>测试支持Chrome浏览器和Firefox浏览器。</p>

<p>此示例是一个公认的精心设计的示例，说明如何使用<code>RTCDataChannel</code>在同一页上的两个对象之间交换数据。
  有关其工作原理的详细信息，请参阅 <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Simple_RTCDataChannel_sample">相应的文章</a>。
</p>

<div class="controlbox">
  <button id="connectButton" name="connectButton" class="buttonleft">
    连接
  </button>
  <button id="disconnectButton" name="disconnectButton" class="buttonright" disabled>
    断开
  </button>
</div>

<div class="messagebox">
  <label for="message">输入消息：
    <input type="text" name="message" id="message" placeholder="消息文本"
           inputmode="latin" size=60 maxlength=120 disabled>
  </label>
  <button id="sendButton" name="sendButton" class="buttonright" disabled>
    发送
  </button>
</div>
<div class="messagebox" id="receivebox">
  <p>收到的消息:</p>
</div>
</body>
</html>
